﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using BFTest.Classes;
using BFTest.Ammunitions;
using BFTest.Particles;
using BFTest.Collision;
using BFTest.Services;

namespace BFTest
{

    class CBattlefieldDGC : DrawableGameComponent, IBattlefieldService
    {
        bool isPaused = false;
        public bool IsPaused { get { return isPaused; } set { isPaused = value; } }

        bool isOver = false;
        int winner = 0;

        ICollisionService collisionService;
        ICollisionService CollisionService { get { return collisionService; } set { collisionService = value; } }

        List<CUnit> playerUnits = new List<CUnit>();
        public List<CUnit> PlayerUnits { get { return playerUnits; } private set { playerUnits = value; } }

        List<CUnit> enemyUnits = new List<CUnit>();
        public List<CUnit> EnemyUnits { get { return enemyUnits; } private set { enemyUnits = value; } }

        List<CUnit> allUnits = new List<CUnit>();
        public List<CUnit> AllUnits { get { return allUnits; } private set { allUnits = value; } }

        Rectangle playAreaSize;
        public Rectangle PlayAreaSize { get { return playAreaSize; } private set { playAreaSize = value; } }

        Texture2D panelTexture;
        public Texture2D PanelTexture { get { return panelTexture; } private set { panelTexture = value; } }

        Rectangle panelArea;
        public Rectangle PanelArea { get { return panelArea; } private set { panelArea = value; } }

        Vector2 panelSize;
        public Vector2 PanelSize { get { return panelSize; } private set { panelSize = value; } }

        SpriteBatch battlefieldBatch;
        public SpriteBatch BattlefieldBatch { get { return battlefieldBatch; } private set { battlefieldBatch = value; } }

        Texture2D pixel;
        public Texture2D Pixel { get { return pixel; } private set { pixel = value; } }
        Texture2D glowTexture;
        public Texture2D GlowTexture { get { return glowTexture; } private set { glowTexture = value; } }
        Texture2D grassTexture;
        public Texture2D GrassTexture { get { return grassTexture; } private set { grassTexture = value; } }
        Texture2D reloadingTexture;
        public Texture2D ReloadingTexture { get { return reloadingTexture; } private set { reloadingTexture = value; } }

        Random rand;
        public Random Rand { get { return rand; } private set { rand = value; } }

        CMessageList messageList;
        public CMessageList MessageList { get { return messageList; } private set { messageList = value; } }
        Vector2 messageListPosition = new Vector2(420, 15);        // Depends on panel graphic

        public bool DebugShowInfo = true;

        MouseState mouseCurrentFrame, mouseLastFrame;
        KeyboardState keyboardCurrentFrame, keyboardLastFrame;

        bool selectionActive = false;
        Vector2 selectionStart;
        Vector2 selectionCurrent;
        Rectangle selectionRect;

        SpriteFont pausedCaptionFont;
        Vector2 pausedCaptionSize;

        public CBattlefieldDGC(Game game) : base(game)
        {
            BattlefieldBatch = new SpriteBatch(game.GraphicsDevice);
        }

        protected override void LoadContent()
        {
            Pixel = Game.Content.Load<Texture2D>("Graphics\\pixel");
            GlowTexture = Game.Content.Load<Texture2D>("Graphics\\circle");
            GrassTexture = Game.Content.Load<Texture2D>("Graphics\\grass");
            ReloadingTexture = Game.Content.Load<Texture2D>("Graphics\\reloading");
            PanelTexture = Game.Content.Load<Texture2D>("Graphics\\panel");
            pausedCaptionFont = Game.Content.Load<SpriteFont>("Fonts\\Paused");
            pausedCaptionSize = pausedCaptionFont.MeasureString("Paused");
        }

        public override void Initialize()
        {
            base.Initialize();

            Rand = new Random();

            PanelSize = new Vector2(PanelTexture.Width, PanelTexture.Height);
            PanelArea = new Rectangle(0, (int)(Game.GraphicsDevice.Viewport.Height - PanelSize.Y), 
                Game.GraphicsDevice.Viewport.Width, (int)PanelSize.Y);
            PlayAreaSize = new Rectangle(0, 0, 
                Game.GraphicsDevice.Viewport.Bounds.Width, 
                (int)(Game.GraphicsDevice.Viewport.Bounds.Height - PanelSize.Y));
            MessageList = new CMessageList(5);

            CollisionService = (ICollisionService)Game.Services.GetService(typeof(ICollisionService));
            CollisionService.AddUnitList(AllUnits);

            RestartGame();       
        }

        public override void Update(GameTime gameTime)
        {
            keyboardCurrentFrame = Keyboard.GetState();
            mouseCurrentFrame = Mouse.GetState();

            if (keyboardCurrentFrame.IsKeyDown(Keys.D) && keyboardLastFrame.IsKeyUp(Keys.D))
                DebugShowInfo = !DebugShowInfo;
            if (keyboardCurrentFrame.IsKeyDown(Keys.P) && keyboardLastFrame.IsKeyUp(Keys.P))
                IsPaused = !IsPaused;
            if (keyboardCurrentFrame.IsKeyDown(Keys.R) && keyboardLastFrame.IsKeyUp(Keys.R))
                RestartGame();
            
            if (!IsPaused)
            {
                // TEMP
                //BFTestGame.Particles.Particles.Add(
                //    new CParticleBlood(
                //        new Vector2(PlayAreaSize.Width / 2, PlayAreaSize.Height / 2), 
                //        new Vector2(Rand.Next(20) - 10, Rand.Next(20) - 10), 
                //        TimeSpan.FromSeconds(10), true));
                
                // Check for a winning side or draw after all bullets are gone
                if (BFTestGame.ActiveBullets.Bullets.Count == 0)
                {
                    bool playerUnitsAlive = false;
                    bool enemyUnitsAlive = false;

                    foreach (CUnit unit in playerUnits)
                    {
                        if (unit.Alive)
                        {
                            playerUnitsAlive = true;
                            break;
                        }
                    }
                    foreach (CUnit unit in enemyUnits)
                    {
                        if (unit.Alive)
                        {
                            enemyUnitsAlive = true;
                            break;
                        }
                    }
                    if (playerUnitsAlive == false && enemyUnitsAlive == false)      // Draw
                        winner = 3;
                    else if (playerUnitsAlive == true && enemyUnitsAlive == false)  // Player won
                        winner = 1;
                    else if (playerUnitsAlive == false && enemyUnitsAlive == true)  // Enemy won
                        winner = 2;
                }

                if (winner == 0 && isOver == false)
                {
                    // No winner yet so update all the units.
                    foreach (CUnit unit in AllUnits)
                    {
                        unit.Update(gameTime);
                    }
                }
                else if(winner > 0 && isOver == false)
                {
                    if (winner == 1)
                        MessageList.Add("You won this battle!");
                    else if (winner == 2)
                        MessageList.Add("The enemy has won this battle!");
                    else if (winner == 3)
                        MessageList.Add("Both sides are dead!");
                    else
                        throw new ArgumentException("winner", "Invalid winner value: " + winner.ToString());

                    isOver = true;
                }
            }
           
            
            if (selectionActive == true)
            {
                selectionCurrent = new Vector2(mouseCurrentFrame.X, mouseCurrentFrame.Y);
                UpdateSelectionRectangle();
            }
            
            if (mouseCurrentFrame.LeftButton == ButtonState.Pressed &&
                mouseLastFrame.LeftButton == ButtonState.Released)
            {
                // Left button clicked

                bool clickedOnUnit = false;

                foreach (CUnit unit in AllUnits)
                {
                    if (mouseCurrentFrame.X >= unit.AABoundingBox.Left &&
                        mouseCurrentFrame.X <= unit.AABoundingBox.Right &&
                        mouseCurrentFrame.Y >= unit.AABoundingBox.Top &&
                        mouseCurrentFrame.Y <= unit.AABoundingBox.Bottom)
                    {
                        // Clicked on a unit

                        if (!keyboardCurrentFrame.IsKeyDown(Keys.LeftControl))
                        {
                            // If Ctrl isn't held down, deselect all units first
                            foreach (CUnit u in AllUnits)
                                u.Selected = false;
                        }

                        unit.Selected = !unit.Selected;
                        clickedOnUnit = true;

                        break;      // Leave the loop here since two units can't be in the same position.
                    }
                }

                if (!clickedOnUnit)
                {
                    // A unit wasn't clicked on so start the selection rectangle
                    selectionActive = true;
                    selectionStart = new Vector2(mouseCurrentFrame.X, mouseCurrentFrame.Y);
                    selectionCurrent = new Vector2(mouseCurrentFrame.X, mouseCurrentFrame.Y);
                    UpdateSelectionRectangle();
                }
            }
            else if (mouseCurrentFrame.LeftButton == ButtonState.Released &&
                mouseLastFrame.LeftButton == ButtonState.Pressed)
            {
                // Left button released

                if (selectionActive)
                {
                    if (!keyboardCurrentFrame.IsKeyDown(Keys.LeftControl))
                        // Ctrl wasn't held down so deselect all units first
                        foreach (CUnit unit in AllUnits)
                            unit.Selected = false;

                    foreach (CUnit unit in AllUnits)
                        if (unit.AABoundingBox.Intersects(selectionRect))
                            unit.Selected = true;

                    selectionActive = false;
                    selectionStart = Vector2.Zero;
                    selectionCurrent = Vector2.Zero;
                }
            }

            mouseLastFrame = mouseCurrentFrame;
            keyboardLastFrame = keyboardCurrentFrame;

            base.Update(gameTime);
        }

        public override void Draw(GameTime gametime)
        {
            GraphicsDevice.Clear(Color.LawnGreen);

            BattlefieldBatch.Begin();

            // Draw grass background
            // TODO: Uncomment background draw when we get a better background image.
            //BattlefieldBatch.Draw(grassTexture, new Rectangle(0, 0, PlayAreaSize.Width, PlayAreaSize.Height), Color.White);

            // Draw bottom panel
            BattlefieldBatch.Draw(PanelTexture, PanelArea, Color.White);

            foreach (CUnit unit in AllUnits)
            {
                unit.Draw(BattlefieldBatch);
            }

            // Draw tooltip on mouse over
            int border = 1;
            int spacing = 2;
            foreach (CUnit unit in AllUnits)
            {
                if (mouseCurrentFrame.X >= unit.AABoundingBox.Left &&
                    mouseCurrentFrame.X <= unit.AABoundingBox.Right &&
                    mouseCurrentFrame.Y >= unit.AABoundingBox.Top &&
                    mouseCurrentFrame.Y <= unit.AABoundingBox.Bottom)
                {
                    string tooltipText = unit.Rank + " " + unit.SoldierName + "\n" + unit.ClassName + "\nHealth: " + unit.Health + "/" + unit.MaxHealth + "\nTarget: " + unit.Target.SoldierName;
                    Rectangle tooltipRect = GetUnitTooltipRect(tooltipText, unit);
                    tooltipRect.Y = tooltipRect.Y - spacing - border;
                    Rectangle tooltipBg = new Rectangle(tooltipRect.X - spacing, tooltipRect.Y - spacing, tooltipRect.Width + (spacing * 2), tooltipRect.Height + (spacing * 2));
                    Rectangle tooltipBorder = new Rectangle(tooltipBg.X - border, tooltipBg.Y - border, tooltipBg.Width + (border * 2), tooltipBg.Height + (border * 2));

                    BattlefieldBatch.Draw(Pixel, tooltipBorder, Color.Black);
                    BattlefieldBatch.Draw(Pixel, tooltipBg, Color.White);
                    BattlefieldBatch.DrawString(BFTestGame.TooltipFont, tooltipText, new Vector2(tooltipRect.X, tooltipRect.Y), Color.Black);

                    break;      // Leave the loop here since two units can't be in the same position.
                }
            }

            // Draw selection rectangle
            if (selectionActive)
                BattlefieldBatch.Draw(Pixel, selectionRect, new Color(0,0,255,100));

            // Draw battlefield's message list
            MessageList.Draw(BattlefieldBatch, new Vector2(messageListPosition.X, PanelArea.Top + messageListPosition.Y), BFTestGame.TooltipFont, Color.Black);

            // If only one unit is selected, draw the last couple of their messages.
            int selectedUnits = 0;
            CUnit selectedUnit = null;
            foreach (CUnit unit in AllUnits)
            {
                if (unit.Selected)
                {
                    selectedUnits++;
                    selectedUnit = unit;        // Remember the selected unit.
                    if (selectedUnits > 1)      // More than one unit is selected so stop.
                        break;
                }
            }
            if (selectedUnits == 1 && selectedUnit != null)
                selectedUnit.Messages.Draw(BattlefieldBatch, new Vector2(15, PanelArea.Top + 15), BFTestGame.TooltipFont, Color.Black, 5);

            // Paused
            if (isPaused)
                BattlefieldBatch.DrawString(pausedCaptionFont, "Paused", new Vector2((panelArea.Width / 2) - (pausedCaptionSize.X / 2), panelArea.Top + 5), Color.Red);

            BattlefieldBatch.End();
        }

        private Rectangle GetUnitTooltipRect(string text, CUnit unit)
        {
            int spacing = 10;
            Vector2 tooltipSize = BFTestGame.TooltipFont.MeasureString(text);
            Rectangle tooltipPosition = new Rectangle(
                (int)(unit.Position.X - (tooltipSize.X / 2)),
                (int)(unit.Position.Y - tooltipSize.Y - spacing),
                (int)tooltipSize.X, (int)tooltipSize.Y);

            if (tooltipPosition.Left < 0)
                tooltipPosition.X = spacing;
            else if (tooltipPosition.Right > BFTestGame.Battlefield.PlayAreaSize.Width)
                tooltipPosition.X = (int)(BFTestGame.Battlefield.PlayAreaSize.Width - tooltipSize.X - spacing);
            if (tooltipPosition.Top < 0)
                tooltipPosition.Y = spacing;
            else if (tooltipPosition.Y > BFTestGame.Battlefield.PlayAreaSize.Height)
                tooltipPosition.Y = (int)(BFTestGame.Battlefield.PlayAreaSize.Height - tooltipSize.Y - spacing);

            return tooltipPosition;
        }

        public void AddUnit(CUnit unit)
        {
            if (unit.Side == 1)
                PlayerUnits.Add(unit);
            else if (unit.Side == 2)
                EnemyUnits.Add(unit);
            else
                throw new ArgumentOutOfRangeException("unit.Side", "unit.Side is not equal to 1 or 2.");

            UpdateAllUnitsList();
        }

        public void RemoveUnit(CUnit unit)
        {
            if (unit.Side == 1)
                PlayerUnits.Remove(unit);
            else if (unit.Side == 2)
                EnemyUnits.Remove(unit);
            else
                throw new ArgumentOutOfRangeException("unit.Side", "unit.Side is not equal to 1 or 2.");

            UpdateAllUnitsList();
        }

        private void UpdateAllUnitsList()
        {
            AllUnits.Clear();
            AllUnits.AddRange(PlayerUnits);
            AllUnits.AddRange(EnemyUnits);
        }

        //TODO: Create Generic Entity Factory and BaseEntity class - stoop
        static int id = 0;
        public int GetUniqueID()
        {     
            // this method was too complicated, We'll never ever hit int.maxvalue unique entities, so this is pretty safe. - stoop
            return ++id;            
        }

        public CUnit UnitFactory(UnitClass unitClass, Vector2 position, string soldierName, UnitRank rank, int side)
        {
            if (side < 1 && side > 2)
                throw new ArgumentException("side", "Unit side must be 1 or 2.");
            else
            {
                switch (unitClass)
                {
                    case UnitClass.Rifleman:
                        return new CRifleman(BFTestGame.Battlefield.GetUniqueID(), position, soldierName, rank, side, Pixel);
                    case UnitClass.MachineGunner:
                        return new CMachineGunner(BFTestGame.Battlefield.GetUniqueID(), position, soldierName, rank, side, Pixel);
                    case UnitClass.Assaultman:
                        return new CAssaultman(BFTestGame.Battlefield.GetUniqueID(), position, soldierName, rank, side, Pixel);
                    default:
                        throw new ArgumentException("unitClass", "Invalid unit class: " + unitClass);
                }
            }
        }

        private void UpdateSelectionRectangle()
        {
            float x, y, width, height;
            
            if (selectionCurrent.X < selectionStart.X)
                x = selectionCurrent.X;
            else
                x = selectionStart.X;

            if (selectionCurrent.Y < selectionStart.Y)
                y = selectionCurrent.Y;
            else
                y = selectionStart.Y;

            width = Math.Abs(selectionCurrent.X - selectionStart.X);
            height = Math.Abs(selectionCurrent.Y - selectionStart.Y);

            selectionRect = new Rectangle((int)x, (int)y, (int)width, (int)height);
        }

        private void PlaceUnits()
        {
            // TODO: Unit's starting position should be determined by their side.
            // ie. all player units will be scattered on the left side and the enemies on the right.
            // Definite positions are given here temporarily. - Booers 11/20/2011
            int numberOfUnits = 10;
            int playerSideX = 75;
            int enemySideX = PlayAreaSize.Width - 75;
            int spacing = (int)(PlayAreaSize.Height / (numberOfUnits + 1));

            // Randomly choose the classes
            Array classes = Enum.GetValues(typeof(UnitClass));
            Array ranks = Enum.GetValues(typeof(UnitRank));
            for (int x = 0; x < numberOfUnits; x++)     // Friendly units
            {
                // Randomly pick a class for each unit.
                UnitClass unitClass = (UnitClass)classes.GetValue(Rand.Next(classes.Length));
                UnitRank unitRank = (UnitRank)ranks.GetValue(Rand.Next(ranks.Length));
                AddUnit(UnitFactory(unitClass, new Vector2(playerSideX, spacing * (x + 1)), NameGenerator.Generate(), unitRank, 1));
            }
            for (int x = 0; x < numberOfUnits; x++)     // Enemy units
            {
                UnitClass unitClass = (UnitClass)classes.GetValue(Rand.Next(classes.Length));
                UnitRank unitRank = (UnitRank)ranks.GetValue(Rand.Next(ranks.Length));
                AddUnit(UnitFactory(unitClass, new Vector2(enemySideX, spacing * (x + 1) - 20), NameGenerator.Generate(), unitRank, 2));
            }  
        }
        
        private void RestartGame()
        {
            PlayerUnits.Clear();
            EnemyUnits.Clear();
            AllUnits.Clear();
            BFTestGame.ActiveBullets.Bullets.Clear();
            MessageList.Messages.Clear();
            winner = 0;
            isOver = false;
            PlaceUnits();
        }

        public CUnit GetUnitByID(int id)
        {
            foreach (CUnit unit in AllUnits)
            {
                if (unit.ID == id)
                    return unit;
            }

            return null;
        }
    }
}
